// Copyright (c) 2025 Hemashushu <hippospark@gmail.com>, All rights reserved.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License version 2.0 and additional exceptions.
// For more details, see the LICENSE, LICENSE.additional, and CONTRIBUTING files.

use crate::{
    context::{Context, Routine},
    object_file::{ObjectFile, MAIN_ROUTE_INDEX},
    transition::ExecuteResult,
    utf8reader::read_char,
};

/// Start a new process.
///
/// A process represents a matching operation.
pub fn start_process(
    context: &mut Context,
    object_file: &ObjectFile,
    start_position: usize,
) -> bool {
    let end = context.bytes.len();

    // Start the main routine for matching.
    start_routine(context, object_file, MAIN_ROUTE_INDEX, start_position, end)
}

/// Start a routine.
///
/// Sub-routines (generated by lookaround assertions) also invoke this function.
/// The `route_index`, `start_position`, and `end_position` parameters specify the route index
/// and the text range for the routine.
pub fn start_routine(
    context: &mut Context,
    object_file: &ObjectFile,
    route_index: usize,
    start_position: usize, // Start position of the text range (inclusive).
    end_position: usize,   // End position of the text range (exclusive).
) -> bool {
    let mut result = false;
    let mut position = start_position;

    let routine = Routine::new(start_position, end_position, route_index);

    // A routine corresponds to a route.
    // Sub-routines (generated by lookaround assertions) also invoke this function.
    // The `context.routines` stack is used to record sub-routines.
    context.routines.push(routine);

    // Continue moving the start position forward and retry matching
    // until a match is successful or the end of the range is reached.
    while position < end_position {
        if execute_transitions(context, object_file, position) {
            result = true;
            break;
        }

        // If the expression starts with "^...", there is no need to try remaining characters.
        if object_file.routes[route_index].is_fixed_start_position {
            break;
        }

        // Move forward by one character and try again.
        let (_, byte_length) = read_char(context.bytes, position);
        position += byte_length;
    }

    context.routines.pop();
    result
}

/// Execute transitions for a route starting from a specified position.
/// Returns `true` if all transitions succeed, otherwise `false`.
fn execute_transitions(context: &mut Context, object_file: &ObjectFile, position: usize) -> bool {
    let (route_index, entry_node_index, exit_node_index) = {
        let thread = context.get_current_routine_ref();
        let route_index = thread.route_index;
        let route = &object_file.routes[route_index];
        (route_index, route.start_node_index, route.end_node_index)
    };

    // Add transitions for the first node (entry node).
    context.push_transitions_of_node(object_file, entry_node_index, position, 0);

    // A route consists of multiple nodes and transitions (similar to functions in programming).
    // When a routine runs, it traverses nodes one by one.
    // Each node contains one or more transitions, which are pushed onto a stack.
    // When a transition is executed, it is popped from the stack. The outcomes are:
    // - On failure: Pop and try the next transition from the stack. If the stack is empty,
    //   the route matching fails.
    // - On success: Move to the next node and push all its transitions onto the stack.
    //   If the node is the last node of the route, the route matching succeeds.
    while let Some(frame) = context.pop_transition_stack_item() {
        let route = &object_file.routes[route_index];
        let node = &route.nodes[frame.current_node_index];
        let transition_item = &node.transition_items[frame.transition_index];

        let position = frame.position;
        let last_repetition_count = frame.repetition_count;
        let transition = &transition_item.transition;
        let target_node_index = transition_item.target_node_index;

        let check_result =
            transition.execute(context, object_file, position, last_repetition_count);

        match check_result {
            ExecuteResult::Success(move_forward, current_repetition_count) => {
                if target_node_index == exit_node_index {
                    // Reached the last node of the route, meaning the route matching succeeded.
                    return true;
                }

                // Add transitions for the next node.
                context.push_transitions_of_node(
                    object_file,
                    target_node_index,
                    position + move_forward,
                    current_repetition_count,
                );
            }
            ExecuteResult::Failure => {
                // Check the next transition.
            }
        }
    }

    // All transitions failed, meaning the route matching failed.
    false
}
